package com.ruoyi.workflow.service.impl;

import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.business.domain.ServiceItem;
import com.ruoyi.business.service.IServiceItemService;
import com.ruoyi.common.constant.HttpStatus;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.page.PageDomain;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.core.page.TableSupport;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.bean.BeanUtils;
import com.ruoyi.system.service.ISysUserService;
import com.ruoyi.workflow.domain.CarPackageAudit;
import com.ruoyi.workflow.mapper.CarPackageAuditMapper;
import com.ruoyi.workflow.service.IBpmnNodeService;
import com.ruoyi.workflow.service.ICarPackageAuditService;
import com.ruoyi.workflow.vo.CarPackageAuditVo;
import com.ruoyi.workflow.vo.HistoricActivity;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.FlowNode;
import org.activiti.bpmn.model.SequenceFlow;
import org.activiti.engine.HistoryService;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
import org.activiti.engine.history.HistoricActivityInstance;
import org.activiti.engine.history.HistoricProcessInstance;
import org.activiti.engine.history.HistoricTaskInstance;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Comment;
import org.activiti.engine.task.Task;
import org.activiti.image.ProcessDiagramGenerator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.io.InputStream;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 套餐审核Service业务层处理
 *
 * @author xiaoliu
 * @date 2022-11-17
 */
@Service
public class CarPackageAuditServiceImpl extends ServiceImpl<CarPackageAuditMapper, CarPackageAudit> implements ICarPackageAuditService {

    @Autowired
    private IServiceItemService serviceItemService;
    @Autowired
    private RuntimeService runtimeService;
    @Autowired
    private TaskService taskService;
    @Autowired
    private IBpmnNodeService bpmnNodeService;
    @Autowired
    private ProcessDiagramGenerator processDiagramGenerator;
    @Autowired
    private RepositoryService repositoryService;
    @Autowired
    private HistoryService historyService;
    @Autowired
    private ISysUserService userService;
    @Autowired
    private ICarPackageAuditService carPackageAuditService;

    /**
     * 查询套餐审核列表
     *
     * @param carPackageAudit 套餐审核
     * @return 套餐审核
     */
    @Override
    public List<CarPackageAudit> selectCarPackageAuditList(CarPackageAudit carPackageAudit) {
        return getBaseMapper().selectCarPackageAuditList(carPackageAudit);
    }

    @Override
    public void audit(Long serviceItemId, String info) {
        // 1. 根据服务项 id 查询服务项对象
        ServiceItem serviceItem = serviceItemService.get(serviceItemId);
        // 2. 校验审核合法性(是套餐 && 状态为初始化), 提示当前无法审核
        if (ServiceItem.CARPACKAGE_NO.equals(serviceItem.getCarPackage())) {
            // 不是套餐
            throw new ServiceException("只有套餐才需要审核");
        }
        if (!(ServiceItem.AUDITSTATUS_INIT.equals(serviceItem.getAuditStatus())
                || ServiceItem.AUDITSTATUS_REPLY.equals(serviceItem.getSaleStatus()))) {
            throw new ServiceException("只有初始化状态才可以审核");
        }
        // 3. 修改服务项状态为审核中
        serviceItemService.changeStatus(serviceItemId, ServiceItem.AUDITSTATUS_AUDITING);

        // 4. 创建一条审核记录信息, 设置相关数据
        CarPackageAudit audit = new CarPackageAudit();
        // 4.1 将服务项信息转换为 json 字符串设置到审核记录中
        audit.setServiceItemId(serviceItemId);
        audit.setServiceItemName(serviceItem.getName());
        audit.setServiceItemPrice(serviceItem.getDiscountPrice());
        audit.setServiceItemInfo(serviceItem.getInfo());

        // 4.2 获取到当前登录用户, 将其 id 设置到审核记录中
        audit.setCreatorId(SecurityUtils.getUserId() + "");
        // 4.3 将审核状态设置为进行中
        audit.setStatus(CarPackageAudit.PACKAGE_AUDIT_STATUS_PENDING);
        audit.setCreateTime(new Date()); // 设置创建时间
        audit.setInfo(info);

        // 4.4 保存审核记录信息
        super.save(audit);

        // 5. 构建流程变量(BusinessKey/金额/审核人员)
        Map<String, Object> map = new HashMap<>();
        // 5.1 BusinessKey = 审核记录的 id
        String businessKey = audit.getId() + "";
        // 5.2 金额 = 服务项的折扣金额
        map.put("money", serviceItem.getDiscountPrice().longValue() + "");
        // 5.3 TODO 审核人员 = 查询当前流程实例对应的节点列表, 获取到每一个节点的人员列表

        // 6. 基于流程定义 Key 开启流程实例, 得到流程实例对象
        ProcessInstance instance = runtimeService.startProcessInstanceByKey(
                CarPackageAudit.CAR_PACKAGE_DEFINITION_KEY,
                businessKey,
                map
        );

        // 6.1 将流程实例 id 设置到审核记录中
        audit.setInstanceId(instance.getId());
        // 6.2 更新审核记录表
        super.updateById(audit);

        // 7. 设置当前任务的候选人, 基于流程实例查询到当前进行中的任务(list/singleResult) => 任务 API TaskService
        Task currentTask = taskService.createTaskQuery()
                .processInstanceId(instance.getId())
                .singleResult();
        // 7.1 基于任务 key 去查询候选人列表
        List<SysUser> userIdList = bpmnNodeService.selectUsersByNodeKey(currentTask.getTaskDefinitionKey());
        // 7.2 调用 activiti 的 api 为指定任务设置候选人
        for (SysUser user : userIdList) {
            taskService.addCandidateUser(currentTask.getId(), user.getUserName());
        }
    }

    @Override
    public InputStream getProcessingImage(String instanceId) {
        // 1. 基于流程实例 id 查询流程实例对象
        HistoricProcessInstance instance = historyService.createHistoricProcessInstanceQuery()
                .processInstanceId(instanceId)
                .singleResult();
        if (instance == null) {
            throw new ServiceException("非法操作");
        }
        // 2. 基于仓库 service 查询 bpmnModel 对象
        BpmnModel bpmnModel = repositoryService.getBpmnModel(instance.getProcessDefinitionId());

        // 3. 获取需要高亮的活动 id list
        List<HistoricActivityInstance> list = historyService.createHistoricActivityInstanceQuery()
                .processInstanceId(instanceId)
                .list();


        List<String> highLightedActivities = list.stream()
                .map(HistoricActivityInstance::getActivityId) // 将活动实例对象转换为活动 id
                .collect(Collectors.toList()); // 将 Stream 收集为一个 List 集合

        //获取高亮线
        List<String> highLightedFlows = getHighLightedFlows(bpmnModel, list);

        return processDiagramGenerator.generateDiagram(bpmnModel, // 模型对象 == 流程文件转换的对象
                highLightedActivities, // 需要高亮的活动 id 集合, 活动 == 所有运行的节点
                highLightedFlows, // 需要高亮的引导线 id 集合
                "宋体",
                "宋体",
                "宋体");
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void cancel(Long id, String reason) {
        // 1. 基于审核记录id查询审核记录对象
        CarPackageAudit audit = getById(id);
        if (audit == null) {
            throw new ServiceException("非法操作");
        }
        // 当前审核记录必须是进行中才可以撤销申请
        if (!CarPackageAudit.PACKAGE_AUDIT_STATUS_PENDING.equals(audit.getStatus())) {
            throw new ServiceException("状态错误, 只有正在进行中的审核才可以撤销");
        }
        // 2. 基于审核记录中的服务项 id, 将服务项状态修改为初始化
        serviceItemService.changeStatus(audit.getServiceItemId(), ServiceItem.AUDITSTATUS_INIT);

        // 3. 将审核记录的状态修改为撤销申请
        audit.setStatus(CarPackageAudit.PACKAGE_AUDIT_STATUS_CANCELED);
        updateById(audit);

        // 4. 调用 activiti 的 api 删除流程实例, 提供删除理由
        runtimeService.deleteProcessInstance(audit.getInstanceId(), reason);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void toAudit(Long id, String taskId, boolean result, String info) {
        //1. 根据审核记录 id 获取审核记录对象, 并判断状态为审核中才允许审核
        //获取审核记录对象
        CarPackageAudit packageAudit = getById(id);
        if (packageAudit == null) {
            throw new ServiceException("非法操作");
        }
        //2. 基于当前用户名作为候选人/负责人条件查询任务信息, 如果查询不到提示非法操作
        String username = SecurityUtils.getUsername();
        Task task = taskService.createTaskQuery()
                .taskCandidateOrAssigned(username)
                .taskId(taskId)
                .singleResult();
        if (task == null) {
            throw new ServiceException("当前用户没有代办事项");
        }
        //3. 设置流程变量(审核结果)
//        Map<String, Object> map = new HashMap<>();
//        map.put(task.getTaskDefinitionKey(), result);
        taskService.setVariable(task.getId(), task.getTaskDefinitionKey(), result);
        //4. 领取任务/完成任务/添加批注
        //领取任务
        taskService.claim(taskId, username);
        //添加批注
        taskService.addComment(taskId, task.getProcessInstanceId(), info);
        //完成任务并设置流程变量
        taskService.complete(taskId);
        //5. 基于流程实例查询下一个进行中的任务, 根据是否有下一个任务判断流程是否结束
        Task nextTask = taskService.createTaskQuery()
                .processInstanceId(task.getProcessInstanceId())
                .singleResult();
        if (nextTask != null) {
            //6. 有下一个任务
            //6.1. 判断当前审核结果是通过还是拒绝
            if (result) {
                //6.2. 如果是审核通过, 基于任务 key 查询候选人列表
                List<SysUser> userList = bpmnNodeService.selectUsersByNodeKey(nextTask.getTaskDefinitionKey());
                //6.2.1. 为下一个任务设置候选人
                for (SysUser sysUser : userList) {
                    taskService.addCandidateUser(nextTask.getId(), sysUser.getUserName());
                }
            } else {
                //6.3. 如果是审核拒绝, 为任务设置候选人为发起人
                //6.3.1. 通过审核记录对象的创建 id, 查询创建人用户对象
                SysUser sysUser = userService.selectUserById(Long.parseLong(packageAudit.getCreatorId()));
                //6.3.2. 将创建人对象的用户名设置到任务的候选人
                taskService.addCandidateUser(nextTask.getId(), sysUser.getUserName());
                //6.3.3. 更新服务项的状态为重新调整
                serviceItemService.changeStatus(packageAudit.getServiceItemId(), ServiceItem.AUDITSTATUS_REPLY);
                //6.3.4跟新审核记录的状态为审核拒绝
                packageAudit.setStatus(CarPackageAudit.PACKAGE_AUDIT_STATUS_REJECTED);
                this.updateById(packageAudit);
            }
        } else {
            //7. 没有任务, 在我们的流程中代表结果一定为通过, 且流程结束
            //7.1. 修改服务项状态为审核通过
            serviceItemService.changeStatus(packageAudit.getServiceItemId(), ServiceItem.AUDITSTATUS_APPROVED);
            //7.2. 修改审核记录状态为审核通过
            packageAudit.setStatus(CarPackageAudit.PACKAGE_AUDIT_STATUS_PASSED);
            this.updateById(packageAudit);
        }
    }

    @Override
    public void updateServiceItem(Long auditId, ServiceItem serviceItem) {
        super.update(new LambdaUpdateWrapper<CarPackageAudit>()
                //套餐名称
                .set(CarPackageAudit::getServiceItemName, serviceItem.getName())
                //套餐备注信息
                .set(CarPackageAudit::getServiceItemInfo, serviceItem.getInfo())
                //套餐审核价格
                .set(CarPackageAudit::getServiceItemPrice, serviceItem.getDiscountPrice())
                .eq(CarPackageAudit::getId, auditId)
        );
    }

    @Override
    public void reapply(Long id, String taskId) {
        // 1. 基于审核记录id查询审核记录对象
        CarPackageAudit packageAudit = getById(id);
        // 2. 验证当前状态是否是审核拒绝, 如果不是审核拒绝, 直接抛出异常
        if (!CarPackageAudit.PACKAGE_AUDIT_STATUS_REJECTED.equals(packageAudit.getStatus())) {
            throw new ServiceException("非法操作");
        }
        // 3. 基于任务 id + 当前用户名查询任务对象, 确认是否是审核人
        String username = SecurityUtils.getUsername();
        Task task = taskService.createTaskQuery()
                .taskId(taskId)
                .taskCandidateOrAssigned(username)
                .singleResult();
        if (task == null) {
            throw new ServiceException("非法操作");
        }
        // 5. 如果能查到, 领取并完成任务, 同时设置流程变量
        taskService.claim(task.getId(), username);
        taskService.complete(task.getId());
        // 6. 查询下一个代办任务
        Task nestTask = taskService.createTaskQuery()
                .processInstanceId(task.getProcessInstanceId())
                .singleResult();
        // 4. 创建流程变量对象, 重新设置 money 的值
        taskService.setVariable(nestTask.getId(), "money", packageAudit.getServiceItemPrice().longValue() + "");
        // 7. 基于该任务查询候选人列表
        List<SysUser> userList = bpmnNodeService.selectUsersByNodeKey(nestTask.getTaskDefinitionKey());
        // 8. 为该任务设置候选人
        for (SysUser sysUser : userList) {
            taskService.addCandidateUser(nestTask.getId(), sysUser.getUserName());
        }
        // 9. 重新将服务项/审核记录的状态修改为审核中
        serviceItemService.changeStatus(packageAudit.getServiceItemId(), ServiceItem.AUDITSTATUS_AUDITING);
        packageAudit.setStatus(CarPackageAudit.PACKAGE_AUDIT_STATUS_PENDING);
        updateById(packageAudit);
    }

    @Override
    public TableDataInfo done(CarPackageAudit carPackageAudit) {
        //1. 接收前端的分页参数
        TableDataInfo info = new TableDataInfo();
        info.setCode(HttpStatus.SUCCESS);
        PageDomain pageDomain = TableSupport.buildPageRequest();
        //2. 以当前用户作为负责人, 统计任务个数
        String username = SecurityUtils.getUsername();
        long total = historyService.createHistoricTaskInstanceQuery()
                .taskAssignee(username)
                .finished()
                .count();
        info.setTotal(total);
        //3. 如果数量为 0, 返回空数据
        if (total == 0) {
            info.setRows(Collections.emptyList());
            return info;
        }
        //4. 如果有数据, 构建分页条件
        int startIndex = (pageDomain.getPageNum() - 1) * pageDomain.getPageSize();
        //5. 以当前用户作为负责人, 查询所有已经完成的任务列表
        List<HistoricTaskInstance> listPage = historyService.createHistoricTaskInstanceQuery()
                .taskAssignee(username)
                .finished()
                .listPage(startIndex, pageDomain.getPageSize());
        //6. 创建一个集合, 用于存储最终返回的数据列表
        List<CarPackageAudit> packageAuditList = new ArrayList<>();
        //7. 遍历任务列表
        for (HistoricTaskInstance task : listPage) {
            //8. 基于任务对象, 得到流程实例 id, 查询流程实例对象
            HistoricProcessInstance instance = historyService.createHistoricProcessInstanceQuery()
                    .processInstanceId(task.getProcessInstanceId())
                    .singleResult();
            //9. 基于流程实例对象, 得到业务标识, 基于业务标识查询审核记录对象
            String businessKey = instance.getBusinessKey();
            CarPackageAudit packageAudit = carPackageAuditService.getById(Long.parseLong(businessKey));
            CarPackageAuditVo vo = new CarPackageAuditVo();
            //10. 将审核记录对象的数据拷贝到 vo 中, 并补充相应字段(完成时间/任务名称/负责人)
            BeanUtils.copyBeanProp(vo, packageAudit);
            vo.setTaskName(task.getName());
            vo.setTaskId(task.getId());
            vo.setFinishTime(task.getEndTime());
            // 审核人
            vo.setAuditors(SecurityUtils.getLoginUser().getUser().getNickName());
            //11. 查询任务的创建人并设置到审核记录
            SysUser sysUser = userService.selectUserById(Long.parseLong(packageAudit.getCreatorId()));
            vo.setCreatorName(sysUser.getNickName());
            //12. 将 vo 对象加入返回的集合中
            packageAuditList.add(vo);
        }
        //13. 返回分页数据
        info.setRows(packageAuditList);
        return info;
    }

    @Override
    public List<HistoricActivity> historyList(String instanceId) {
        //1. 基于流程实例id查询到历史任务(已完成)列表
        List<HistoricActivityInstance> taskList = historyService.createHistoricActivityInstanceQuery()
                .processInstanceId(instanceId)
                .activityType("userTask")
                .orderByHistoricActivityInstanceStartTime()
                .asc()
                .list();
        List<HistoricActivity> list = new ArrayList<>(taskList.size());
        //2. 遍历历史任务集合, 基于任务id查询批注信息
        for (HistoricActivityInstance instance : taskList) {
            HistoricActivity historicActivity = new HistoricActivity();
            BeanUtils.copyBeanProp(historicActivity, instance);
            //获取批注信息
            List<Comment> comments = taskService.getTaskComments(instance.getTaskId(), "comment");
            if (!CollectionUtils.isEmpty(comments)) {
                historicActivity.setComment(comments.get(0).getFullMessage());
            }
            //获取负责人名称
            SysUser sysUser = userService.selectUserByUserName(instance.getAssignee());
            if (sysUser != null) {
                historicActivity.setAssigneeName(sysUser.getNickName());
            }

            list.add(historicActivity);
        }
        //3. 将批注信息保存到对象, 最终返回数据
        return list;
    }

    /**
     * 获取已经流转的线
     *
     * @param bpmnModel
     * @param historicActivityInstances
     * @return
     */
    private List<String> getHighLightedFlows(BpmnModel bpmnModel, List<HistoricActivityInstance> historicActivityInstances) {
        // 高亮流程已发生流转的线id集合
        List<String> highLightedFlowIds = new ArrayList<>();
        // 全部活动节点
        List<FlowNode> historicActivityNodes = new ArrayList<>();
        // 已完成的历史活动节点
        List<HistoricActivityInstance> finishedActivityInstances = new ArrayList<>();

        for (HistoricActivityInstance historicActivityInstance : historicActivityInstances) {
            FlowNode flowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(historicActivityInstance.getActivityId(), true);
            historicActivityNodes.add(flowNode);
            // 结束时间不为空，则是已完成节点
            if (historicActivityInstance.getEndTime() != null) {
                finishedActivityInstances.add(historicActivityInstance);
            }
        }

        FlowNode currentFlowNode;
        FlowNode targetFlowNode;
        // 遍历已完成的活动实例，从每个实例的outgoingFlows中找到已执行的
        for (HistoricActivityInstance currentActivityInstance : finishedActivityInstances) {
            // 获得当前活动对应的节点信息及outgoingFlows信息
            currentFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(currentActivityInstance.getActivityId(), true);
            List<SequenceFlow> sequenceFlows = currentFlowNode.getOutgoingFlows();

            /**
             * 遍历outgoingFlows并找到已流转的 满足如下条件认为已已流转：
             * 1.当前节点是并行网关或兼容网关，则通过outgoingFlows能够在历史活动中找到的全部节点均为已流转
             * 2.当前节点是以上两种类型之外的，通过outgoingFlows查找到的时间最早的流转节点视为有效流转
             */
            if ("parallelGateway".equals(currentActivityInstance.getActivityType()) || "inclusiveGateway".equals(currentActivityInstance.getActivityType())) {
                // 遍历历史活动节点，找到匹配流程目标节点的
                for (SequenceFlow sequenceFlow : sequenceFlows) {
                    targetFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(sequenceFlow.getTargetRef(), true);
                    if (historicActivityNodes.contains(targetFlowNode)) {
                        highLightedFlowIds.add(targetFlowNode.getId());
                    }
                }
            } else {
                List<Map<String, Object>> tempMapList = new ArrayList<>();
                for (SequenceFlow sequenceFlow : sequenceFlows) {
                    for (HistoricActivityInstance historicActivityInstance : historicActivityInstances) {
                        if (historicActivityInstance.getActivityId().equals(sequenceFlow.getTargetRef())) {
                            Map<String, Object> map = new HashMap<>();
                            map.put("highLightedFlowId", sequenceFlow.getId());
                            map.put("highLightedFlowStartTime", historicActivityInstance.getStartTime().getTime());
                            tempMapList.add(map);
                        }
                    }
                }

                if (!CollectionUtils.isEmpty(tempMapList)) {
                    // 遍历匹配的集合，取得开始时间最早的一个
                    long earliestStamp = 0L;
                    String highLightedFlowId = null;
                    for (Map<String, Object> map : tempMapList) {
                        long highLightedFlowStartTime = Long.valueOf(map.get("highLightedFlowStartTime").toString());
                        if (earliestStamp == 0 || earliestStamp >= highLightedFlowStartTime) {
                            highLightedFlowId = map.get("highLightedFlowId").toString();
                            earliestStamp = highLightedFlowStartTime;
                        }
                    }
                    highLightedFlowIds.add(highLightedFlowId);
                }

            }

        }
        return highLightedFlowIds;
    }

}
